#include "ak_internal.h"
#include "ak_probe.h"
#include "lsm_public.h"
/* For exporting variables and functions. */
struct ak_security_exports ak_sec_exports;

static struct security_operations *ak360_security_ops = NULL;
static struct security_operations original_security_ops;

#ifndef MAY_ACCESS
#define MAY_ACCESS 16
#endif
#ifndef MAY_OPEN
#define MAY_OPEN 32
#endif
#ifndef MAY_DEL
#define MAY_DEL 64
#endif
#ifndef MAY_RENAME
#define MAY_RENAME 128
#endif
#ifndef MAY_SEC
#define MAY_SEC 130
#endif

#define WRITE_MASK (MAY_WRITE|MAY_APPEND|MAY_DEL|MAY_RENAME)

#include "sec_kernel_header.h"
#if 0
struct vfsmount *sec_vfsmount(struct dentry *fdentry)
{
    struct list_head *head, *pos = NULL;
    struct mount *mnt = NULL;
  
    struct vfsmount *vfsmnt = NULL;
    struct super_block *sb_root = fdentry->d_sb;
    int flag = 0;
    
#if LINUX_VERSION_CODE > KERNEL_VERSION(3,0,101)
    head = &(current->nsproxy->mnt_ns->root->mnt_list);
    list_for_each(pos, head){
        mnt = list_entry(pos, struct mount, mnt_list);
        vfsmnt = &(mnt->mnt);
        if(vfsmnt->mnt_sb == sb_root){
            flag = 1;
            break;
        }
    }
#elif (LINUX_VERSION_CODE < KERNEL_VERSION(3,0,101) && LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32))
    head = &(current->nsproxy->mnt_ns->root->mnt_list);
    list_for_each(pos, head){
        vfsmnt = list_entry(pos, struct vfsmount, mnt_list);
        if(vfsmnt->mnt_sb == sb_root){
            flag = 1;
            break;
        }
    }
#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32) && LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) || (LINUX_VERSION_CODE == KERNEL_VERSION(3,0,101)) 
    head = &(current->nsproxy->mnt_ns->list);
    list_for_each(pos, head){
        vfsmnt = list_entry(pos, struct vfsmount, mnt_list);
        if(vfsmnt->mnt_sb == sb_root){
            flag = 1;
            break;
        }
    }
#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
    head = &(current->namespace->list);
    list_for_each(pos, head){
        vfsmnt = list_entry(pos, struct vfsmount, mnt_list);
        if(vfsmnt->mnt_sb == sb_root){
            flag = 1;
            break;
        }
    }
#else
    printkinfo(DBG_DEBUG,"sec_path() function is not defined !\n");
#endif
    if(flag == 1)
        return vfsmnt;
    else
    {
        return NULL;
    }
}
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,12) || LINUX_VERSION_CODE == KERNEL_VERSION(3,10,0)
static inline int mnt_has_parent(struct mount *mnt)
{
        return mnt != mnt->mnt_parent;
}

static struct mount* get_root(struct mount *p)
{
        struct mount *pa = p;
        for(;mnt_has_parent(pa); pa = pa->mnt_parent)
        {
                ;
        }
        return pa;
}

static struct mount* next_mnt(struct mount *p, struct mount *root)
{
    struct list_head *next = p->mnt_mounts.next;
    if (next == &p->mnt_mounts) {
        while (1) {
            if (p == root)
                return NULL;
            next = p->mnt_child.next;
            if (next != &p->mnt_parent->mnt_mounts)
                break;
            p = p->mnt_parent;
        }
    }
    return list_entry(next, struct mount, mnt_child);
}
#endif

struct vfsmount *sec_vfsmount(struct dentry *fdentry)
{

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,1,12) && LINUX_VERSION_CODE != KERNEL_VERSION(3,10,0)
    struct list_head *head, *pos = NULL;
#if LINUX_VERSION_CODE > KERNEL_VERSION(3,0,101)
    struct mount *mnt = NULL;  
#endif

#else
    struct fs_struct *fs = current->fs;
    struct mount *pm = NULL, *root = NULL;
#endif
  
    struct vfsmount *vfsmnt = NULL;
    struct super_block *sb_root = fdentry->d_sb;
    int flag = 0;
	
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,12)  || LINUX_VERSION_CODE == KERNEL_VERSION(3,10,0)
    root = container_of(fs->root.mnt, struct mount, mnt);
    root = get_root(root);  
    for(pm = root; pm; pm = next_mnt(pm,root))
    {
        vfsmnt = &(pm->mnt);
        if(vfsmnt->mnt_sb == sb_root)
        {
            flag = 1;
            break;
        }
    } 
#elif LINUX_VERSION_CODE > KERNEL_VERSION(3,0,101)
    head = &(current->nsproxy->mnt_ns->root->mnt_list);
    list_for_each(pos, head){
        mnt = list_entry(pos, struct mount, mnt_list);
        vfsmnt = &(mnt->mnt);
        if(vfsmnt->mnt_sb == sb_root){
            flag = 1;
            break;
        }
    }
#elif (LINUX_VERSION_CODE < KERNEL_VERSION(3,0,101) && LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32))
    head = &(current->nsproxy->mnt_ns->root->mnt_list);
    list_for_each(pos, head){
        vfsmnt = list_entry(pos, struct vfsmount, mnt_list);
        if(vfsmnt->mnt_sb == sb_root){
            flag = 1;
            break;
        }
    }
#elif (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32) && LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)) || (LINUX_VERSION_CODE == KERNEL_VERSION(3,0,101)) 
    head = &(current->nsproxy->mnt_ns->list);
    list_for_each(pos, head){
        vfsmnt = list_entry(pos, struct vfsmount, mnt_list);
        if(vfsmnt->mnt_sb == sb_root){
            flag = 1;
            break;
        }
    }
#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
    head = &(current->namespace->list);
    list_for_each(pos, head){
        vfsmnt = list_entry(pos, struct vfsmount, mnt_list);
        if(vfsmnt->mnt_sb == sb_root){
            flag = 1;
            break;
        }
    }
#else
    printk("sec_path() function is not defined !\n");
#endif
    if(flag == 1)
        return vfsmnt;
    else
    {
        return NULL;
    }
}

int check_procname(char *procname)
{
    return 0;
    #if 0
    if (0 == strcmp(procname, "/usr/bin/vi") ||
        0 == strcmp(procname, "/usr/bin/vim") ||
        0 == strcmp(procname, "/usr/bin/touch") ||
        0 == strcmp(procname, "/usr/bin/cat") ||
        0 == strcmp(procname, "/usr/bin/chmod") ||
        0 == strcmp(procname, "/usr/bin/rm") ||
        0 == strcmp(procname, "/usr/bin/ls"))
     #endif
     if (0 == strcmp(procname, "/usr/bin/cp"))
     {
        return 1;
     }
     else
        return 0;
}
int check_filename(char *filename)
{
    return 0;
    if (strstr(filename, "/share/swf/") || strstr(filename, "/home/"))
    {
        return 1;
    }
    else
        return 0;
}
#include "epp_netlink.h"
msg_permission_t gl_strecord = {
    .szfilename = {0},
    .szpermission = {0},
    .permission = 0
};

void set_test_file(msg_data_t *pstdata)
{
    int permission = 0;
    if (0 == strcmp(pstdata->szpermission, "r"))
    {
        permission = MAY_READ;
    }
    else if (0 == strcmp(pstdata->szpermission, "w"))
    {
        permission = MAY_WRITE;
    }
    else if (0 == strcmp(pstdata->szpermission, "rw"))
    {
        permission = MAY_READ | MAY_WRITE;
    }
    else if (0 == strcmp(pstdata->szpermission, "none"))
    {
        //permission = 0;
    }
    else if (0 == strcmp(pstdata->szpermission, "rename"))
    {
        permission = MAY_READ | MAY_WRITE;
    }
    else
    {
        permission = MAY_READ | MAY_WRITE;
    }
    strcpy(gl_strecord.szfilename, pstdata->szfilename);
    strcpy(gl_strecord.szpermission, pstdata->szpermission);
    gl_strecord.permission = permission;

    return;
}

int send_message_to_user(char *msg, int len)
{
    return nelk_send(msg, len);
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)
static int ak360_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode)
{
    int ret = 0;
    char *procname = NULL;
    struct vfsmount *vfsmnt = sec_vfsmount(dentry); 
    struct path path = {
        .dentry = dentry,
        .mnt = vfsmnt,
    };
    
    char *filename = ak_get_realpath(&path);
	if (filename)
	{
	    if (check_filename(filename))
		    printk("%s filename = %s\n", __FUNCTION__, filename);
		if (0 == strcmp(filename, gl_strecord.szfilename))
		{
            if (!(gl_strecord.permission & MAY_WRITE))
            {
                char szmessage[1024] = {0};
                snprintf(szmessage, 1024, "inode_create %s failed.", filename);
                printk("%s\n", szmessage);
                send_message_to_user(szmessage, 1024);
                ret = -1;
            }
		}
		kfree(filename);
	}
	else
    {
        printk("%s get filename failed.\n", __FUNCTION__);
    }

	procname = (char *)ak_get_procname();
    if (procname)
    {
        if (check_procname(procname))
        printk("%s procname = %s\n", __FUNCTION__, procname);
        kfree(procname);
    }
    
    return ret;
}
#else
static int ak360_inode_create(struct inode *dir, struct dentry *dentry, int mode)
{
    return 0;
}
#endif
static int ak360_inode_mkdir(struct inode *dir, struct dentry *dentry,  umode_t mode)
{
    char *procname = NULL;
    struct vfsmount *vfsmnt = sec_vfsmount(dentry); 
    
    struct path path = {
        .dentry = dentry,
        .mnt = vfsmnt,
    };
    
    char *filename = ak_get_realpath(&path);
	if (filename)
	{
	    if (check_filename(filename))
		    printk("%s filename = %s\n", __FUNCTION__, filename);
		kfree(filename);
	}

	procname = (char *)ak_get_procname();
    if (procname)
    {
        if (check_procname(procname))
        printk("%s procname = %s\n", __FUNCTION__, procname);
        kfree(procname);
    }

    return 0;
}

static int ak360_inode_rmdir(struct inode *dir,  struct dentry *dentry)
{
    char *procname = NULL;
    struct vfsmount *vfsmnt = sec_vfsmount(dentry); 
    struct path path = {
        .dentry = dentry,
        .mnt = vfsmnt,
    };
    
    char *filename = ak_get_realpath(&path);
	if (filename)
	{
	    if (check_filename(filename))
		    printk("%s filename = %s\n", __FUNCTION__, filename);
		kfree(filename);
	}

	procname = (char *)ak_get_procname();
    if (procname)
    {
        if (check_procname(procname))
            printk("%s procname = %s\n", __FUNCTION__, procname);
        kfree(procname);
    }

    return 0;
}
static int ak360_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
{
    char *procname = NULL;
    char *oldfilename = NULL;
    char *newfilename = NULL;
    struct vfsmount *old_vfsmnt = sec_vfsmount(old_dentry);
    struct vfsmount *new_vfsmnt = sec_vfsmount(new_dentry); 
    struct path oldpath = {
        .dentry = old_dentry,
        .mnt = old_vfsmnt,
    };

    struct path newpath = {
        .dentry = new_dentry,
        .mnt = new_vfsmnt,
    };
    
    oldfilename = ak_get_realpath(&oldpath);
	if (oldfilename)
	{
	    if (check_filename(oldfilename))
		    printk("%s oldfilename = %s\n", __FUNCTION__, oldfilename);
		kfree(oldfilename);
	}

	newfilename = ak_get_realpath(&newpath);
	if (newfilename)
	{
	    if (check_filename(newfilename))
		    printk("%s newfilename = %s\n", __FUNCTION__, newfilename);
		kfree(newfilename);
	}
	
	procname = (char *)ak_get_procname();
    if (procname)
    {
        if (check_procname(procname))
        printk("%s procname = %s\n", __FUNCTION__, procname);
        kfree(procname);
    }

    return 0;
}
static int ak360_inode_unlink(struct inode *dir, struct dentry *dentry)
{
    int ret = 0;
    char *procname = NULL;
    struct vfsmount *vfsmnt = sec_vfsmount(dentry); 
    struct path path = {
        .dentry = dentry,
        .mnt = vfsmnt,
    };
    
    char *filename = ak_get_realpath(&path);
	if (filename) {
	    if (check_filename(filename))
		    printk("%s filename = %s\n", __FUNCTION__, filename);
		if (0 == strcmp(filename, gl_strecord.szfilename))
		{
            if (!(gl_strecord.permission & MAY_WRITE))
            {
                char szmessage[1024] = {0};
                snprintf(szmessage, 1024, "inode_unlink %s failed.", filename);
                printk("%s\n", szmessage);
                send_message_to_user(szmessage, 1024);
                ret = -1;
            }
		}
		kfree(filename);
	}

	procname = (char *)ak_get_procname();
    if (procname)
    {
        if (check_procname(procname))
            printk("%s procname = %s\n", __FUNCTION__, procname);
        kfree(procname);
    }

    return ret;
}
static int ak360_inode_rename(struct inode *old_inode,
                      struct dentry *old_dentry,
                      struct inode *new_inode,
                      struct dentry *new_dentry)
{
    int ret = 0;
    char *oldfilename = NULL;
    char *newfilename = NULL;
    char *procname = NULL;

    struct vfsmount *old_vfsmnt = sec_vfsmount(old_dentry);
    struct vfsmount *new_vfsmnt = sec_vfsmount(new_dentry); 
    struct path oldpath = {
        .dentry = old_dentry,
        .mnt = old_vfsmnt,
    };

    struct path newpath = {
        .dentry = new_dentry,
        .mnt = new_vfsmnt,
    };
    
    oldfilename = ak_get_realpath(&oldpath);
	if (oldfilename)
	{
	    if (check_filename(oldfilename))
		    printk("%s oldfilename = %s\n", __FUNCTION__, oldfilename);
		if (0 == strcmp(oldfilename, gl_strecord.szfilename))
    	{
    	    if (0 == strcmp("rename", gl_strecord.szpermission))
    	    {
                char szmessage[1024] = {0};
                snprintf(szmessage, 1024, "inode_rename %s failed.", oldfilename);
                printk("%s\n", szmessage);
                send_message_to_user(szmessage, 1024);
                ret = -1;
            }
    	}
		kfree(oldfilename);
	}


	newfilename = ak_get_realpath(&newpath);
	if (newfilename)
	{
	    if (check_filename(newfilename))
		    printk("%s newfilename = %s\n", __FUNCTION__, newfilename);
		kfree(newfilename);
	}
	
	procname = (char *)ak_get_procname();
    if (procname)
    {
        if (check_procname(procname))
            printk("%s procname = %s\n", __FUNCTION__, procname);
        kfree(procname);
    }

    return ret;
}
static int ak360_inode_symlink(struct inode *inode, struct dentry *dentry, const char *old_name)
{
    struct path path = {
        .dentry = dentry,
        .mnt = NULL,
    };
    
    char *filename = ak_get_realpath(&path);
	if (filename)
	{
	    if (check_filename(filename))
		    printk("%s filename = %s\n", __FUNCTION__, filename);
		kfree(filename);
	}

    return 0;
}

static int ak360_inode_setattr(struct dentry *dentry, struct iattr *attr)
{
    struct vfsmount *vfsmnt = sec_vfsmount(dentry); 
    struct path path = {
        .dentry = dentry,
        .mnt = vfsmnt,
    };
    char *procname = NULL;
    char *filename = ak_get_realpath(&path);
	if (filename)
	{
	    if (check_filename(filename))
		    printk("%s filename = %s\n", __FUNCTION__, filename);
		kfree(filename);
	}

	procname = (char *)ak_get_procname();
    if (procname)
    {
        if (check_procname(procname))
            printk("%s procname = %s\n", __FUNCTION__, procname);
        kfree(procname);
    }

    return 0;
}

static int ak360_bprm_check_security(struct linux_binprm *bprm)
{
	char *procname = ak_get_file_realpath(bprm->file);
    if (procname)
    {
        if (check_procname(procname))
            printk("%s procname is %s\n", __FUNCTION__, procname);
        kfree(procname);
    }

    if (NULL != bprm)
    {
        //printk("current->fs->pwd = %s\n",current->fs->pwd.mnt->mnt_root->d_name.name);
        //printk("current->fs->root = %s\n",current->fs->root.mnt->mnt_root->d_name.name);
        //printk("bprm->filename is %s\n",bprm->filename);
    }
    return 0;
}

static int ak360_file_permission(struct file *file, int mask)
{
    int ret = 0;
    char *procname = NULL;
	char *filename = ak_get_file_realpath(file);
    if (filename)
    {
        if (check_filename(filename))
        {
            printk("%s filename is %s, mask is %d\n", __FUNCTION__, filename,mask);
            if(mask & WRITE_MASK)
            {
                printk("will modify %s filename is %s, mask is %d\n", __FUNCTION__, filename,mask);
            }
        }
        if (0 == strcmp(filename, gl_strecord.szfilename))
		{
		    printk("filename is %s, permission is %d\n", gl_strecord.szfilename, gl_strecord.permission);
            if (!(gl_strecord.permission & MAY_WRITE) && (mask & WRITE_MASK))
            {
                char szmessage[1024] = {0};
                snprintf(szmessage, 1024, "file_permission write %s failed.", filename);
                printk("%s\n", szmessage);
                send_message_to_user(szmessage, 1024);
                ret = -1;
            }
            if (!(gl_strecord.permission & MAY_READ) && (mask & MAY_READ))
            {
                char szmessage[1024] = {0};
                snprintf(szmessage, 1024, "file_permission read %s failed.", filename);
                printk("%s\n", szmessage);
                send_message_to_user(szmessage, 1024);
                ret = -1;
            }
		}
        kfree(filename);
    }
    else
    {
        printk("%s get filename failed.\n", __FUNCTION__);
    }
    
    procname = (char *)ak_get_procname();
    if (procname)
    {
        if (check_procname(procname))
            printk("%s procname = %s\n", __FUNCTION__, procname);
        kfree(procname);
    }
    
    return ret;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
static int ak360_file_open(struct file *file, const struct cred *cred)
{
    int ret = 0;
    char *procname = NULL;
    char *filename = ak_get_file_realpath(file);
    if (filename)
    {
        if (check_filename(filename))
            printk("%s filename is %s\n", __FUNCTION__, filename);
        kfree(filename);
    }
    else
    {
        printk("%s get filename failed.\n", __FUNCTION__);
    }
	procname = (char *)ak_get_procname();
    if (procname)
    {
        if (check_procname(procname))
            printk("%s procname = %s\n", __FUNCTION__, procname);
        kfree(procname);
    }

	return ret;
}
#else
static int ak360_dentry_open(struct file *file, const struct cred *cred)
{
	return 0;
}
#endif

#define swap_security_ops(op)						\
	original_security_ops.op = ak360_security_ops->op; smp_wmb(); ak360_security_ops->op = ak360_##op;
#define recover_security_ops(op)						\
	ak360_security_ops->op = original_security_ops.op;

/***************************************************************************************
Function:ak_update_security_ops
Description:注册LSM钩子函数
Input:
    void
Output:
    void
Return:
    int
        0 Success
        Others    Fail
Caution:
Others:
***************************************************************************************/
void ak_update_security_ops(void)
{
    swap_security_ops(inode_create);
    swap_security_ops(inode_mkdir);
    swap_security_ops(inode_rmdir);
    swap_security_ops(inode_link);
    swap_security_ops(inode_unlink);
    swap_security_ops(inode_symlink);
    swap_security_ops(inode_rename);
    swap_security_ops(inode_setattr);
    
    swap_security_ops(bprm_check_security);
    swap_security_ops(file_permission);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
    swap_security_ops(file_open);
#else
    swap_security_ops(dentry_open);
#endif

    return;
}
void ak_unload_security_ops(void)
{
    recover_security_ops(inode_create);
    recover_security_ops(inode_mkdir);
    recover_security_ops(inode_rmdir);
    recover_security_ops(inode_link);
    recover_security_ops(inode_unlink);
    recover_security_ops(inode_symlink);
    recover_security_ops(inode_rename);
    recover_security_ops(inode_setattr);

    recover_security_ops(bprm_check_security);
    recover_security_ops(file_permission);


#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0)
        recover_security_ops(file_open);
#else
        recover_security_ops(dentry_open);
#endif

    return;
}

int __init ak_sec_init(void)
{
	struct security_operations *ops = probe_security_ops();
	if (!ops)
	{
		goto out;
    }
    ak360_security_ops = ops;
    
    ak_sec_exports.find_task_by_vpid = probe_find_task_by_vpid();
    if (!ak_sec_exports.find_task_by_vpid)
		goto out;
    ak_sec_exports.find_task_by_pid_ns = probe_find_task_by_pid_ns();
    if (!ak_sec_exports.find_task_by_pid_ns)
        goto out;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36)
    ak_sec_exports.vfsmount_lock = probe_vfsmount_lock();
    if (!ak_sec_exports.vfsmount_lock)
        goto out;
#elif LINUX_VERSION_CODE < KERNEL_VERSION(3, 2, 0)
    ak_sec_exports.__d_path = probe___d_path();
    if (!ak_sec_exports.__d_path)
        goto out;
#else
    ak_sec_exports.d_absolute_path = probe_d_absolute_path();
    if (!ak_sec_exports.d_absolute_path)
        goto out;
#endif

    ak_update_security_ops();
    return 0;
out:
    return -EINVAL;
}


void ak_sec_exit(void)
{
    //unsigned long cr0;
    //cr0 = clear_and_return_cr0();
    ak_unload_security_ops();
    //setback_cr0(cr0);

    return;
}
